package idbx

import (
	"context"
	"database/sql"
	log "github.com/sirupsen/logrus"
	"github.com/tietang/dbx"
)

const TX = "tx"

type BaseDao struct {
	TX *sql.Tx
}

func (d *BaseDao) SetTx(tx *sql.Tx) {
	d.TX = tx
}

type txFunc func(*dbx.TxRunner) error

func Tx(fn func(*dbx.TxRunner) error) error {
	return TxContext(context.Background(), fn)
}

func TxContext(ctx context.Context, fn func(runner *dbx.TxRunner) error) error {
	return DbxDatabase().Tx(fn)
}

//将runner绑定到上下文
func WithValueContext(parent context.Context, runner *dbx.TxRunner) context.Context {
	return context.WithValue(parent, TX, runner)
}
func ExecuteContext(ctx context.Context, fn func(*dbx.TxRunner) error) error {
	tx := ctx.Value(TX).(*dbx.TxRunner)
	return fn(tx)
}

type MutiTx struct {
	dbs   []*dbx.Database
	funcs []txFunc
}

func (m *MutiTx) RunTx() (err error) {
	size := len(m.dbs)
	runners := make([]*dbx.TxRunner, size)

	for i, db := range m.dbs {
		tx, err := db.Begin()
		runner := dbx.NewTxRunner(tx)
		runner.EntityMapper = db.EntityMapper
		runner.LoggerSettings = db.LoggerSettings
		runner.ILogger = db.Logger()
		runners[i] = runner
		if err != nil {
			return err
		}
	}
	//defer func() {
	//    for i, tx := range txs {
	//        if err := tx.Commit(); err != nil {
	//            log.Error(err)
	//            rollback(i, txs)
	//            return err
	//        }
	//    }
	//}()

	for i, runner := range runners {

		if err := m.funcs[i](runner); err != nil {
			rollback(i, runners)
			return err
		}
	}

	for i, tx := range runners {
		if err := tx.Commit(); err != nil {
			log.Error(err)
			rollback(i, runners)
			return err
		}
	}
	return nil
}

func rollback(index int, runners []*dbx.TxRunner) {
	for i := 0; i < index; i++ {
		if err := runners[i].Rollback(); err != nil {
			log.Error(err)
		}
	}
}

func GetOne(runner *dbx.TxRunner, out interface{}) {
	ok, err := runner.GetOne(out)
	if err != nil {
		log.Error(err)
		out = nil
	}
	if !ok {
		out = nil
	}
}

func Get(runner *dbx.TxRunner, out interface{}, sql string, params ...interface{}) {
	ok, err := runner.Get(out, sql, params...)
	if err != nil {
		log.Error(err)
		out = nil
	}
	if !ok {
		out = nil
	}
}
